/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2006 Tim Angus

This file is part of Tremulous.

Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Tremulous is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// g_weapon.c
// perform the server side effects of a weapon firing

#include "g_local.h"

static  vec3_t  forward, right, up;
static  vec3_t  muzzle;

/*
================
G_ForceWeaponChange
================

    //prevent reloading a weapon twice
    if( ent->client->ps.weaponstate == WEAPON_RELOADING && weapon )
    {
      BG_UnpackAmmoArray( ent->client->ps.weapon, ent->client->ps.ammo, ent->client->ps.powerups, &ammo, &clips );
      BG_FindAmmoForWeapon( ent->client->ps.weapon, NULL, &maxClips );

      if( maxClips > 0 )
      {
        clips--;
        BG_FindAmmoForWeapon( ent->client->ps.weapon, &ammo, NULL );
      }

      if( BG_FindUsesEnergyForWeapon( ent->client->ps.weapon ) &&
          BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) )
        ammo = (int)( (float)ammo * BATTPACK_MODIFIER );

      BG_PackAmmoArray( ent->client->ps.weapon, ent->client->ps.ammo, ent->client->ps.powerups, ammo, clips );
    }

*/
void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon )
{
  int i;
  int ammo, clips, maxClips;

  if( ent )
  {
    ent->client->ps.pm_flags |= PMF_WEAPON_SWITCH;

    if( weapon == WP_NONE 
      || !BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ))
    {
      //switch to the first non blaster weapon
      for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
      {
        if( i == WP_BLASTER )
          continue;

        if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) )
        {
          ent->client->ps.persistant[ PERS_NEWWEAPON ] = i;
          break;
        }
      }

      //only got the blaster to switch to
	  if( i == WP_NUM_WEAPONS ) 
        ent->client->ps.persistant[ PERS_NEWWEAPON ] = WP_BLASTER;
	}
	else
      ent->client->ps.persistant[ PERS_NEWWEAPON ] = weapon;
   
    // force this here to prevent flamer effect from continuing 
    ent->client->ps.generic1 = WPM_NOTFIRING;

    ent->client->ps.weapon = ent->client->ps.persistant[ PERS_NEWWEAPON ];
  }
}

/*
=================
G_GiveClientMaxAmmo
=================
*/
void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo )
{
  int       i;
  int       maxAmmo, maxClips;
  qboolean  weaponType, restoredAmmo = qfalse;

  for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
  {
    if( buyingEnergyAmmo )
      weaponType = BG_FindUsesEnergyForWeapon( i );
    else
      weaponType = !BG_FindUsesEnergyForWeapon( i );

    if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) &&
        weaponType && !BG_FindInfinteAmmoForWeapon( i ) &&
        !BG_WeaponIsFull( i, ent->client->ps.stats,
          ent->client->ps.ammo, ent->client->ps.powerups ) )
    {
      BG_FindAmmoForWeapon( i, &maxAmmo, &maxClips );

      if( buyingEnergyAmmo )
      {
        G_AddEvent( ent, EV_RPTUSE_SOUND, 0 );

        if( BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) )
          maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER );
		else if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) )
          maxAmmo = (int)( (float)maxAmmo * BSUIT_MODIFIER );
      }

      BG_PackAmmoArray( i, ent->client->ps.ammo, ent->client->ps.powerups,
                        maxAmmo, maxClips );

      restoredAmmo = qtrue;
    }
  }

  if( restoredAmmo )
    G_ForceWeaponChange( ent, ent->client->ps.weapon );
}

/*
=================
G_CheckBarb
=================
*/
void G_CheckBarb( gentity_t *ent )
{
  gclient_t *client = ent->client;

  if( client->ps.weapon == WP_ALEVEL3_UPG )
  {
	int ammo, maxAmmo;
	
	BG_FindAmmoForWeapon( WP_ALEVEL3_UPG, &maxAmmo, NULL );
	BG_UnpackAmmoArray( WP_ALEVEL3_UPG, client->ps.ammo, client->ps.powerups, &ammo, NULL );
	
	if( ammo < maxAmmo ) {
	  if( !client->barbCheck ) client->barbCheck = level.time;
	  if( (level.time - client->barbCheck) > 5000 && client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) {
		ammo++;
		BG_PackAmmoArray( WP_ALEVEL3_UPG, client->ps.ammo, client->ps.powerups, ammo, 0 );
		client->barbCheck = 0;
	  } else if( (level.time - client->barbCheck) > 10000 ) {
		  ammo++;
		  BG_PackAmmoArray( WP_ALEVEL3_UPG, client->ps.ammo, client->ps.powerups, ammo, 0 );
		  client->barbCheck = 0;
	  }
	} else client->barbCheck = 0;
  }
}

/*
================
G_BounceProjectile
================
*/
void G_BounceProjectile( vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout )
{
  vec3_t v, newv;
  float dot;

  VectorSubtract( impact, start, v );
  dot = DotProduct( v, dir );
  VectorMA( v, -2 * dot, dir, newv );

  VectorNormalize(newv);
  VectorMA(impact, 8192, newv, endout);
}

/*
================
G_WideTrace

Trace a bounding box against entities, but not the world
Also check there is a line of sight between the start and end point
================
*/
static void G_WideTrace( trace_t *tr, gentity_t *ent, float range, float width, gentity_t **target )
{
  vec3_t    mins, maxs;
  vec3_t    end;

  VectorSet( mins, -width, -width, -width );
  VectorSet( maxs, width, width, width );

  *target = NULL;

  if( !ent->client )
    return;

  // Set aiming directions
  AngleVectors( ent->client->ps.viewangles, forward, right, up );
  CalcMuzzlePoint( ent, forward, right, up, muzzle );
  VectorMA( muzzle, range, forward, end );

  G_UnlaggedOn( ent, muzzle, range );

  // Trace against entities
  trap_Trace( tr, muzzle, mins, maxs, end, ent->s.number, CONTENTS_BODY );
  if( tr->entityNum != ENTITYNUM_NONE )
  {
    *target = &g_entities[ tr->entityNum ];

	// Set range to the trace length plus the width, so that the end of the
    // LOS trace is close to the exterior of the target's bounding box
    range = Distance( muzzle, tr->endpos ) + width;
    VectorMA( muzzle, range, forward, end );

    // Trace for line of sight against the world
    trap_Trace( tr, muzzle, NULL, NULL, end, 0, CONTENTS_SOLID );
    if( tr->fraction < 1.0f )
      *target = NULL;
  }

  G_UnlaggedOff( );
}


/*
======================
SnapVectorTowards
SnapVectorNormal

Round a vector to integers for more efficient network
transmission, but make sure that it rounds towards a given point
rather than blindly truncating.  This prevents it from truncating
into a wall.
======================
*/
void SnapVectorTowards( vec3_t v, vec3_t to )
{
  int   i;

  for( i = 0 ; i < 3 ; i++ )
  {
    if( to[ i ] <= v[ i ] )
      v[ i ] = (int)v[ i ];
    else
      v[ i ] = (int)v[ i ] + 1;
  }
}

void SnapVectorNormal( vec3_t v, vec3_t normal )
{
  int i;

  for( i = 0 ; i < 3 ; i++ )
  {
    if( v[ i ] >= 0 )
      v[ i ] = (int)( v[ i ] + ( normal[ i ] <= 0 ? 0 : 1 ) );
    else
      v[ i ] = (int)( v[ i ] + ( normal[ i ] <= 0 ? -1 : 0 ) );
  }
}

/*
===============
meleeAttack
===============
*/
void meleeAttack( gentity_t *ent, float range, float width, int damage, meansOfDeath_t mod )
{
  trace_t   tr;
  vec3_t    end;
  gentity_t *tent;
  gentity_t *traceEnt;
  vec3_t    mins, maxs;

  VectorSet( mins, -width, -width, -width );
  VectorSet( maxs, width, width, width );

  // set aiming directions
  AngleVectors( ent->client->ps.viewangles, forward, right, up );

  CalcMuzzlePoint( ent, forward, right, up, muzzle );

  VectorMA( muzzle, range, forward, end );

  G_UnlaggedOn( ent, muzzle, range );
  // first try an exact hit in case the width of the trace is too wide
  trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
  if( ( tr.surfaceFlags & SURF_NOIMPACT ) || tr.entityNum < 0 ||
        tr.entityNum >= MAX_GENTITIES ||
        !g_entities[ tr.entityNum ].takedamage )
  {
    // cast a wider net
    trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
  }
  G_UnlaggedOff( );

  if( tr.surfaceFlags & SURF_NOIMPACT )
    return;

  traceEnt = &g_entities[ tr.entityNum ];

  // send blood impact
  if( traceEnt->takedamage && traceEnt->client )
  {
    tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
    tent->s.otherEntityNum = traceEnt->s.number;
    tent->s.eventParm = DirToByte( tr.plane.normal );
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
	if( ent->client->ps.weapon == WP_ABUILD )
	  G_Sound( traceEnt, CHAN_VOICE, G_SoundIndex( "models/weapons/abuildupg/impactflesh0.wav" ) );
  }

  if( traceEnt->takedamage )
    G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod, 1.0 );
}

/*
======================================================================

MACHINEGUN

======================================================================
*/

void bulletFire( gentity_t *ent, float spread, int damage, int mod )
{
  trace_t   tr;
  vec3_t    end;
  float   r;
  float   u;
  gentity_t *tent;
  gentity_t *traceEnt;

  r = random( ) * M_PI * 2.0f;
  u = sin( r ) * crandom( ) * spread * 16;
  r = cos( r ) * crandom( ) * spread * 16;
  VectorMA( muzzle, 8192 * 16, forward, end );
  VectorMA( end, r, right, end );
  VectorMA( end, u, up, end );

  // don't use unlagged if this is not a client (e.g. turret)
  if( ent->client )
  {
    G_UnlaggedOn( ent, muzzle, 8192 * 16 );
    trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
    G_UnlaggedOff( );
  }
  else
    trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );

  if( tr.surfaceFlags & SURF_NOIMPACT )
    return;

  traceEnt = &g_entities[ tr.entityNum ];

  // snap the endpos to integers, but nudged towards the line
  SnapVectorTowards( tr.endpos, muzzle );

  // send bullet impact
  if( traceEnt->takedamage && traceEnt->client )
  {
    tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
    tent->s.eventParm = traceEnt->s.number;
  }
  else
  {
    tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL );
    tent->s.eventParm = DirToByte( tr.plane.normal );
  }
  tent->s.otherEntityNum = ent->s.number;

  if( traceEnt->takedamage )
  {
    G_Damage( traceEnt, ent, ent, forward, tr.endpos,
      damage, 0, mod, 1.0 );
  }
}

/*
======================================================================

SHOTGUN

======================================================================
*/

// this should match CG_ShotgunPattern
void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent )
{
  int        i;
  float      r, u;
  vec3_t    end;
  vec3_t    forward, right, up;
  trace_t    tr;
  gentity_t  *traceEnt;

  // derive the right and up vectors from the forward vector, because
  // the client won't have any other information
  VectorNormalize2( origin2, forward );
  PerpendicularVector( right, forward );
  CrossProduct( forward, right, up );

  // generate the "random" spread pattern
  for( i = 0; i < SHOTGUN_PELLETS; i++ )
  {
    r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
    u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
    VectorMA( origin, 8192 * 16, forward, end );
    VectorMA( end, r, right, end );
    VectorMA( end, u, up, end );

    trap_Trace( &tr, origin, NULL, NULL, end, ent->s.number, MASK_SHOT );
    traceEnt = &g_entities[ tr.entityNum ];

    // send bullet impact
    if( !( tr.surfaceFlags & SURF_NOIMPACT ) )
    {
      if( traceEnt->takedamage )
        G_Damage( traceEnt, ent, ent, forward, tr.endpos,  SHOTGUN_DMG, 0, MOD_SHOTGUN, 1.0 );
    }
  }
}


void shotgunFire( gentity_t *ent )
{
  gentity_t    *tent;

  // send shotgun blast
  tent = G_TempEntity( muzzle, EV_SHOTGUN );
  VectorScale( forward, 4096, tent->s.origin2 );
  SnapVector( tent->s.origin2 );
  tent->s.eventParm = rand() & 255;    // seed for spread pattern
  tent->s.otherEntityNum = ent->s.number;
  G_UnlaggedOn( ent, muzzle, 8192 * 16 );
  ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );
  G_UnlaggedOff();
}

/*
======================================================================

MASS DRIVER

======================================================================
*/


#define MDRIVER_MAX_HITS 10

void massDriverFire( gentity_t *ent )
{
  trace_t tr;
  vec3_t hitPoints[ MDRIVER_MAX_HITS ], hitNormals[ MDRIVER_MAX_HITS ],
         origin, originToEnd, muzzleToEnd, muzzleToOrigin, end;
  gentity_t *traceEnts[ MDRIVER_MAX_HITS ], *traceEnt, *tent;
  float length_offset;
  int i, hits = 0, skipent;

  // loop through all entities hit by a line trace
  G_UnlaggedOn( ent, muzzle, 8192 * 16 );
  VectorMA( muzzle, 8192 * 16, forward, end );
  VectorCopy( muzzle, tr.endpos );
  skipent = ent->s.number;
  for( i = 0; i < MDRIVER_MAX_HITS && skipent != ENTITYNUM_NONE; i++ )
  {
    trap_Trace( &tr, tr.endpos, NULL, NULL, end, skipent, MASK_SHOT );
    if( tr.surfaceFlags & SURF_NOIMPACT )
      break;
    traceEnt = &g_entities[ tr.entityNum ];
	if( traceEnt == ent ) continue;
    skipent = tr.entityNum;
    if( traceEnt->s.eType == ET_PLAYER )
    {
      // don't travel through teammates with FF off
      if( OnSameTeam( ent, traceEnt ) && !level.instaGibMode &&
          ( !g_friendlyFire.integer || !g_friendlyFireHumans.integer ) )
        skipent = ENTITYNUM_NONE;
    }
    else if( traceEnt->s.eType == ET_BUILDABLE )
    {
      // don't travel through team buildables with FF off
      if( traceEnt->biteam == ent->client->pers.teamSelection &&
          !g_friendlyBuildableFire.integer )
        skipent = ENTITYNUM_NONE;
    }
    else
      skipent = ENTITYNUM_NONE;

    // save the hit entity, position, and normal
    VectorCopy( tr.endpos, hitPoints[ hits ] );
    VectorCopy( tr.plane.normal, hitNormals[ hits ] );
    SnapVectorNormal( hitPoints[ hits ], tr.plane.normal );
    traceEnts[ hits++ ] = traceEnt;
  }

  for( i = 0; i < hits; i++ )
  {
    vec3_t muzzleToPos;
    float length;

	traceEnt = traceEnts[ i ];

    // send impact
    if( traceEnt->takedamage && traceEnt->client )
	{
	  tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
	  tent->s.otherEntityNum = traceEnt->s.number;
	  tent->s.eventParm = traceEnt->s.number;
	  tent->s.weapon = ent->s.weapon;
	  tent->s.generic1 = ent->s.generic1; //weaponMode
	}
    else if( !traceEnt->client && hits > 1 )
    {
      tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
      tent->s.eventParm = traceEnt->s.number;
      tent->s.weapon = ent->s.weapon;
      tent->s.generic1 = ent->s.generic1; // weaponMode
    }
    else if( hits <= 1 )
    {
      tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
      tent->s.eventParm = DirToByte( tr.plane.normal );
      tent->s.weapon = ent->s.weapon;
      tent->s.generic1 = ent->s.generic1; // weaponMode
    }
    
	if( traceEnt->takedamage ) {
		int damage = MDRIVER_DMG;
		if( level.instaGibMode ) damage *= 1000;
		G_Damage( traceEnt, ent, ent, forward, tr.endpos,
			damage, 0, MOD_MDRIVER, 1.0 );
	}
	// We can jump :O
	else if( hits <= 1 && level.instaGibMode && g_MDriverSplash.integer )
	{
		G_RadiusDamage( tr.endpos, ent, g_MDriverSplash.integer,
			70, NULL, MOD_MDRIVER, 60.0 );
	}
  }
  
  G_UnlaggedOff( );
}



/*
======================================================================

LOCKBLOB

======================================================================
*/

void lockBlobLauncherFire( gentity_t *ent )
{
  gentity_t *m;

  m = fire_lockblob( ent, muzzle, forward );

//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
}

/*
======================================================================

HIVE

======================================================================
*/

void hiveFire( gentity_t *ent )
{
  gentity_t *m;

   vec3_t origin;
 	  	 
	m = fire_hive( ent, muzzle, forward ); 	   // Fire from the hive tip, not the center
    VectorMA( muzzle, ent->r.maxs[ 2 ], ent->s.origin2, origin );

//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
}

/*
======================================================================

BLASTER PISTOL

======================================================================
*/

void blasterFire( gentity_t *ent )
{
  gentity_t *m;

  m = fire_blaster( ent, muzzle, forward );

//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
}

/*
======================================================================

PULSE RIFLE

======================================================================
*/

void pulseRifleFire( gentity_t *ent )
{
  gentity_t *m;

  m = fire_pulseRifle( ent, muzzle, forward );

//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
}

/*
======================================================================

FLAME THROWER

======================================================================
*/

void flamerFire( gentity_t *ent )
{
  gentity_t *m;

  if ( ( level.time - ent->client->lastSpatAtTime ) < ( g_grangerExtinguisher.integer * 1000 ) )
  {
    ent->client->ps.extinguished = qtrue;
    ent->client->ps.weaponstate = WEAPON_READY;
    ent->client->ps.generic1 = WPM_NOTFIRING;
    return;
  }

  m = fire_flamer( ent, muzzle, forward );
}

/*
======================================================================

GRENADE

======================================================================
*/

void throwGrenade( gentity_t *ent )
{
  gentity_t *m;

  m = launch_grenade( ent, muzzle, forward );
}

/*
======================================================================

LAS GUN

======================================================================
*/

/*
===============
lasGunFire
===============
*/
void lasGunFire( gentity_t *ent )
{
  trace_t   tr;
  vec3_t    end;
  gentity_t *tent;
  gentity_t *traceEnt;

  VectorMA( muzzle, 8192 * 16, forward, end );

  G_UnlaggedOn( ent, muzzle, 8192 * 16 );
  trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
  G_UnlaggedOff( );

  if( tr.surfaceFlags & SURF_NOIMPACT )
    return;

  traceEnt = &g_entities[ tr.entityNum ];

  // snap the endpos to integers, but nudged towards the line
  SnapVectorTowards( tr.endpos, muzzle );

  // send impact
  if( traceEnt->takedamage && traceEnt->client )
  {
    tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
    tent->s.otherEntityNum = traceEnt->s.number;
    tent->s.eventParm = DirToByte( tr.plane.normal );
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }
  else
  {
    tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
    tent->s.eventParm = DirToByte( tr.plane.normal );
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  if( traceEnt->takedamage )
    G_Damage( traceEnt, ent, ent, forward, tr.endpos, LASGUN_DAMAGE, 0, MOD_LASGUN, 1.0 );
}

/*
======================================================================

PAIN SAW

======================================================================
*/

void painSawFire( gentity_t *ent )
{
  trace_t   tr;
  vec3_t    end;
  gentity_t *tent;
  gentity_t *traceEnt;

  // set aiming directions
  AngleVectors( ent->client->ps.viewangles, forward, right, up );

  CalcMuzzlePoint( ent, forward, right, up, muzzle );

  VectorMA( muzzle, PAINSAW_RANGE, forward, end );

  G_UnlaggedOn( ent, muzzle, PAINSAW_RANGE );
  trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
  G_UnlaggedOff( );

  if( tr.surfaceFlags & SURF_NOIMPACT )
    return;

  traceEnt = &g_entities[ tr.entityNum ];

  // send blood impact
  if( traceEnt->takedamage )
  {
    vec3_t  temp;

    //hack to get the particle system to line up with the weapon
    VectorCopy( tr.endpos, temp );
    temp[ 2 ] -= 10.0f;

    if( traceEnt->client )
    {
      tent = G_TempEntity( temp, EV_MISSILE_HIT );
      tent->s.otherEntityNum = traceEnt->s.number;
    }
    else
      tent = G_TempEntity( temp, EV_MISSILE_MISS );

    tent->s.eventParm = DirToByte( tr.plane.normal );
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  if( traceEnt->takedamage )
    G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PAINSAW, 1.0 );
}

/*
======================================================================

LUCIFER CANNON

======================================================================
*/

/*
===============
LCChargeFire
===============
*/
void LCChargeFire( gentity_t *ent, qboolean secondary )
{
  gentity_t *m;

  if( secondary )
  {
    m = fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE,
      LCANNON_SECONDARY_RADIUS );
    ent->client->ps.weaponTime = LCANNON_REPEAT;
  }
  else
  {
    m = fire_luciferCannon( ent, muzzle, forward, ent->client->ps.stats[ STAT_MISC ], LCANNON_RADIUS );
    ent->client->ps.weaponTime = LCANNON_CHARGEREPEAT;
  }

  ent->client->ps.stats[ STAT_MISC ] = 0;
}

/*
======================================================================

TESLA GENERATOR

======================================================================
*/


void teslaFire( gentity_t *self )
{
  trace_t tr;
  vec3_t origin, target;
  gentity_t *tent;
  
  if( !self->enemy )
    return;

  // Move the muzzle from the entity origin up a bit to fire over turrets
  VectorMA( muzzle, self->r.maxs[ 2 ], self->s.origin2, origin );

  // Don't aim for the center, aim at the top of the bounding box
  VectorCopy( self->enemy->s.origin, target );
  target[ 2 ] += self->enemy->r.maxs[ 2 ];

  // Trace to the target entity
  trap_Trace( &tr, origin, NULL, NULL, target, self->s.number, MASK_SHOT );
  if( tr.entityNum != self->enemy->s.number )
    return;

  // Client side firing effect
  self->s.eFlags |= EF_FIRING;

  // Deal damage
  if( self->enemy->takedamage )
  {
    vec3_t dir;
    
    VectorSubtract( target, origin, dir );
    G_Damage( self->enemy, self, self, dir, tr.endpos,
              TESLAGEN_DMG, 0, MOD_TESLAGEN, 1.0 );
  }

  // Send tesla zap trail
  tent = G_TempEntity( tr.endpos, EV_TESLATRAIL );
  tent->s.generic1 = self->s.number; // src
  tent->s.clientNum = self->enemy->s.number; // dest
}


/*
======================================================================

BUILD GUN

======================================================================
*/

/*
===============
CheckCkitRepair
===============
*/
void CheckCkitRepair( gentity_t* ent )
{
  vec3_t      forward, end;
  trace_t     tr;
  gentity_t   *traceEnt;
  int         bHealth;

  if( ent->client->ps.weaponTime ) return;

  if( (level.time-ent->client->lastRepairTime)<BG_FindRepeatRate1ForWeapon( ent->client->ps.weapon ) ) return;

  // Construction kit repair
  AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
  VectorMA( ent->client->ps.origin, 100, forward, end );

  trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, 
	  MASK_PLAYERSOLID );
  traceEnt = &g_entities[ tr.entityNum ];

  if( tr.fraction < 1.0 && traceEnt->spawned && traceEnt->health > 0 &&
	  traceEnt->s.eType == ET_BUILDABLE && traceEnt->biteam == PTE_HUMANS )
  {
	if( ent->client->ps.stats[ STAT_MISC ] > 0 )
	{
	  //G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum );
	  return;
	}

	bHealth = BG_FindHealthForBuildable( traceEnt->s.modelindex );
	if( traceEnt->health < bHealth )
	{
	  traceEnt->health += HBUILD_HEALRATE;
	  ent->client->lastRepairTime = level.time;
      ent->client->pers.statscounters.repairspoisons++;
      level.humanStatsCounters.repairspoisons++;
	  if( traceEnt->health >= bHealth )
	  {
		traceEnt->health = bHealth;
		G_AddEvent( ent, EV_BUILD_REPAIRED, 0 );
	  }
	  else
		G_AddEvent( ent, EV_BUILD_REPAIR, 0 );
	}
  }
}


/* 
=============== 
cancelBuildFire 
=============== 
*/ 
void cancelBuildFire( gentity_t *ent ) 
{ 
  // Cancel ghost buildable 
  if( ent->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) 
  { 
	ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; 
	return; 
  } 

  if( ent->client->ps.weapon == WP_HBUILD || ent->client->ps.weapon == WP_HBUILD2 )
  {
	vec3_t      forward, end;
	trace_t     tr;
	gentity_t   *traceEnt;

	// Search for a target
	AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
	VectorMA( ent->client->ps.origin, 100, forward, end );
	trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, 
		MASK_PLAYERSOLID );
	traceEnt = &g_entities[ tr.entityNum ];
  
	if( tr.fraction < 1.0 && traceEnt->s.eType == ET_PLAYER && OnSameTeam( ent, traceEnt ) && traceEnt->health < 100 )
	{
	  if( ent->health < 5 )
	  {
	    G_AddEvent( ent, EV_BUILD_REPAIRED, 0 );
		return;
	  }
	  else
	  {
		  ent->health -= 7;
		  traceEnt->health +=7;

		  ent->client->lastRepairTime = level.time;
		  if( traceEnt->health >= 100 )
		  {
			traceEnt->health = 100;
			G_AddEvent( ent, EV_BUILD_REPAIRED, 0 );
		  }
		  else
			G_AddEvent( ent, EV_BUILD_REPAIR, 0 );
	  }
	}
  }

  if( ent->client->ps.weapon == WP_ABUILD2 )
	meleeAttack( ent, ABUILDER_CLAW_RANGE, ABUILDER_CLAW_WIDTH,
	    ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW );
  else if( ent->client->ps.weapon == WP_ABUILD && g_improvedGameplay.integer ) {
    G_Sound( ent, CHAN_VOICE, G_SoundIndex( "models/weapons/abuildupg/flash0.wav" ) );
	meleeAttack( ent, ABUILDER_CLAW_RANGE, ABUILDER_CLAW_WIDTH,
	    ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW );
  }
}

/*
===============
buildFire
===============
*/
void buildFire( gentity_t *ent, dynMenu_t menu )
{
  if( ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
  {
    if( ent->client->ps.stats[ STAT_MISC ] > 0 )
    {
      G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum );
      return;
    }

    if( G_BuildIfValid( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) )
    {
      if( g_cheats.integer || g_instantBuild.integer )
      {
        ent->client->ps.stats[ STAT_MISC ] = 0;
      }
      else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_IsOvermindBuilt( ) )
      {
        ent->client->ps.stats[ STAT_MISC ] +=
          BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2;
      }
      else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && !G_IsPowered( muzzle ) &&
          ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) != BA_H_REPEATER ) //hack
      {
        ent->client->ps.stats[ STAT_MISC ] +=
          BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2;
      }
      else
        ent->client->ps.stats[ STAT_MISC ] +=
          BG_FindBuildDelayForWeapon( ent->s.weapon );

      ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;

      // don't want it bigger than 32k
      if( ent->client->ps.stats[ STAT_MISC ] > 30000 )
        ent->client->ps.stats[ STAT_MISC ] = 30000;

	if( ent->suicideTime > 0 ) ent->suicideTime = 0;
    }
    return;
  }

  G_TriggerMenu( ent->client->ps.clientNum, menu );
}

/*
=================
builderShieldFire
=================
*/
void builderShieldFire( gentity_t *ent )
{
  trace_t   tr;
  vec3_t    end, mins, maxs, diffPos;
  gentity_t *traceEnt;
  int       entityList[MAX_GENTITIES];
  int       i, num;
  vec3_t    push;
  float     angle, lenPos, lenForward, lenRange;
  float     knockback, scale;

  vec3_t range = {HBUILD_SHIELD_RANGE, HBUILD_SHIELD_RANGE, HBUILD_SHIELD_RANGE};
  knockback = (float)g_hbuildKnockback.integer;
  
  AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
  lenForward = VectorLength(forward);
  lenRange = VectorLength(range);


  VectorMA( muzzle, HBUILD_SHIELD_RANGE, forward, end );
  
  VectorAdd( ent->client->ps.origin, range, maxs );
  VectorSubtract( ent->client->ps.origin, range, mins );
  
  G_UnlaggedOn( ent, muzzle, HBUILD_SHIELD_RANGE );

  num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES);
  //DEBUG
  //VectorSet( mins, -HBUILD_SHIELD_WIDTH, -HBUILD_SHIELD_WIDTH, -HBUILD_SHIELD_WIDTH );
  //VectorSet( maxs, HBUILD_SHIELD_WIDTH, HBUILD_SHIELD_WIDTH, HBUILD_SHIELD_WIDTH );
  for ( i=0; i<num; i++)
  {
    traceEnt = &g_entities[ entityList[ i ] ];
    if (traceEnt == ent)
      continue;
    
    VectorSubtract( traceEnt->s.origin, ent->s.origin, diffPos );
    lenPos = VectorLength( diffPos );
    angle = acos( DotProduct(forward, diffPos) / (lenForward * lenPos));
    
    if( (lenPos < lenRange && Q_fabs( angle ) < HBUILD_SHIELD_TOLERANCE) &&
        (traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_NONE) ) 
    {
      trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, traceEnt->s.origin, ent->s.number, MASK_SHOT);
      if( tr.entityNum != ENTITYNUM_WORLD && &g_entities[tr.entityNum] == traceEnt )
      {
        knockback *= BG_FindKnockbackScaleForClass(traceEnt->client->ps.stats[STAT_PCLASS]) / 20;
        
        // linear decreasing
        knockback *= ( (-1/lenRange)+1 )*(lenRange-lenPos)/1000;
        push[0]=diffPos[0]*knockback;
        push[1]=diffPos[1]*knockback;
        push[2]=( diffPos[2] * knockback/2 );
        VectorAdd(traceEnt->client->ps.velocity, push, traceEnt->client->ps.velocity); // push the alien away from the builder
        
        // velocity downscaling
        knockback *= ( 2+(-1/lenRange) )*(lenRange-lenPos);
        knockback *= DotProduct(traceEnt->client->ps.velocity, forward) / lenForward;
        if (knockback < 0)
        {
          traceEnt->client->ps.velocity[0] /= -knockback;
          traceEnt->client->ps.velocity[1] /= -knockback;
          VectorScale(traceEnt->client->ps.velocity, 1/knockback, traceEnt->client->ps.velocity);
          G_Printf(va("%f, %f, %f\n", traceEnt->client->ps.velocity[0], traceEnt->client->ps.velocity[1], traceEnt->client->ps.velocity[2]));
        }
      }
    }
  }
  
  G_UnlaggedOff( );
  //so the client side knows
  ent->s.eFlags |= EF_FIRING;

  // snap the endpos to integers to save net bandwidth, but nudged towards the line
  SnapVectorTowards( tr.endpos, muzzle );

//  // send railgun beam effect
//  tent = G_TempEntity( tr.endpos, EV_TESLATRAIL );
//
//  VectorCopy( muzzle, tent->s.origin2 );
//
//  tent->s.generic1 = ent->s.number; //src
//  tent->s.clientNum = traceEnt->s.number; //dest

  // move origin a bit to come closer to the drawn gun muzzle
  //VectorMA( tent->s.origin2, 28, up, tent->s.origin2 );
}

void slowBlobFire( gentity_t *ent )
{
  gentity_t *m;

  m = fire_slowBlob( ent, muzzle, forward );

//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
}


/*
======================================================================

LEVEL0

======================================================================
*/

/*
===============
CheckVenomAttack
===============
*/
qboolean CheckVenomAttack( gentity_t *ent )
{
  trace_t   tr;
  vec3_t    end;
  gentity_t *tent;
  gentity_t *traceEnt;
  vec3_t    mins, maxs;
  int       damage = LEVEL0_BITE_DMG;

  VectorSet( mins, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH );
  VectorSet( maxs, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH );

  // set aiming directions
  AngleVectors( ent->client->ps.viewangles, forward, right, up );

  CalcMuzzlePoint( ent, forward, right, up, muzzle );

  VectorMA( muzzle, LEVEL0_BITE_RANGE, forward, end );

  G_UnlaggedOn( ent, muzzle, LEVEL0_BITE_RANGE );
  trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
  G_UnlaggedOff( );

  if( tr.surfaceFlags & SURF_NOIMPACT )
    return qfalse;

  traceEnt = &g_entities[ tr.entityNum ];

  if( !traceEnt->takedamage )
    return qfalse;

 if( traceEnt->health <= 0 )
	return qfalse;

  if( !traceEnt->client && !traceEnt->s.eType == ET_BUILDABLE )
    return qfalse;

  //allow bites to work against defensive buildables only
  if( traceEnt->s.eType == ET_BUILDABLE )
  {
    if( traceEnt->s.modelindex != BA_H_MGTURRET &&
        traceEnt->s.modelindex != BA_H_TESLAGEN )
      return qfalse;

    //hackery
    damage *= 0.5f;
  }

  if( traceEnt->client )
  {
    if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
      return qfalse;
    if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 )
      return qfalse;
  }

  // send blood impact
  if( traceEnt->takedamage && traceEnt->client )
  {
    tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
    tent->s.otherEntityNum = traceEnt->s.number;
    tent->s.eventParm = DirToByte( tr.plane.normal );
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_LEVEL0_BITE, 1.0 );

  return qtrue;
}

/*
======================================================================

LEVEL1

======================================================================
*/

/*
===============
CheckGrabAttack
===============
*/
void CheckGrabAttack( gentity_t *ent )
{
  trace_t   tr;
  vec3_t    end, dir;
  gentity_t *traceEnt;

  // set aiming directions
  AngleVectors( ent->client->ps.viewangles, forward, right, up );

  CalcMuzzlePoint( ent, forward, right, up, muzzle );

  VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end );

  trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
  if( tr.surfaceFlags & SURF_NOIMPACT )
    return;

  traceEnt = &g_entities[ tr.entityNum ];

  if( !traceEnt->takedamage )
    return;

  if( traceEnt->client )
  {
    if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
      return;

    if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 )
      return;

    if( !( traceEnt->client->ps.stats[ STAT_STATE ] & SS_GRABBED ) )
    {
      AngleVectors( traceEnt->client->ps.viewangles, dir, NULL, NULL );
      traceEnt->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir );

      //event for client side grab effect
      G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 );
    }

    traceEnt->client->ps.stats[ STAT_STATE ] |= SS_GRABBED;

    if( ent->client->ps.weapon == WP_ALEVEL1 )
      traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_TIME;
    else if( ent->client->ps.weapon == WP_ALEVEL1_UPG )
      traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_U_TIME;
  }
  else if( traceEnt->s.eType == ET_BUILDABLE &&
      traceEnt->s.modelindex == BA_H_MGTURRET )
  {
    if( !traceEnt->lev1Grabbed )
      G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 );

    traceEnt->lev1Grabbed = qtrue;
    traceEnt->lev1GrabTime = level.time;
  }
}

/*
===============
poisonCloud
===============
*/
void poisonCloud( gentity_t *ent )
{
  int       entityList[ MAX_GENTITIES ];
  vec3_t    range = { LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE };
  vec3_t    mins, maxs;
  int       i, num;
  gentity_t *humanPlayer;
  trace_t   tr;

  VectorAdd( ent->client->ps.origin, range, maxs );
  VectorSubtract( ent->client->ps.origin, range, mins );

  G_UnlaggedOn( ent, ent->client->ps.origin, LEVEL1_PCLOUD_RANGE );
  num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
  for( i = 0; i < num; i++ )
  {
    humanPlayer = &g_entities[ entityList[ i ] ];

    if( humanPlayer->client && humanPlayer->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
    {
      if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, humanPlayer->client->ps.stats ) )
        continue;

      if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, humanPlayer->client->ps.stats ) )
        continue;

      trap_Trace( &tr, muzzle, NULL, NULL, humanPlayer->s.origin, humanPlayer->s.number, MASK_SHOT );

      //can't see target from here
      if( tr.entityNum == ENTITYNUM_WORLD )
        continue;

      if( !( humanPlayer->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) )
      {
        humanPlayer->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED;
        humanPlayer->client->lastPoisonCloudedTime = level.time;
        humanPlayer->client->lastPoisonCloudedClient = ent;
        trap_SendServerCommand( humanPlayer->client->ps.clientNum, "poisoncloud" );
      }
    }
  }
  G_UnlaggedOff( );
}


/*
======================================================================

LEVEL2

======================================================================
*/

#define MAX_ZAPS  64

static zap_t  zaps[ MAX_CLIENTS ];

/*
===============
G_FindNewZapTarget
===============
*/
static gentity_t *G_FindNewZapTarget( gentity_t *ent )
{
  int       entityList[ MAX_GENTITIES ];
  vec3_t    range = { LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE };
  vec3_t    mins, maxs;
  int       i, j, k, num;
  gentity_t *enemy;
  trace_t   tr;

  VectorScale( range, 1.0f / M_ROOT3, range );
  VectorAdd( ent->s.origin, range, maxs );
  VectorSubtract( ent->s.origin, range, mins );

  num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );

  for( i = 0; i < num; i++ )
  {
    enemy = &g_entities[ entityList[ i ] ];

    if( ( ( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
        ( enemy->s.eType == ET_BUILDABLE &&
          BG_FindTeamForBuildable( enemy->s.modelindex ) == BIT_HUMANS ) ) && enemy->health > 0 )
    {
      qboolean foundOldTarget = qfalse;

      trap_Trace( &tr, muzzle, NULL, NULL, enemy->s.origin, ent->s.number, MASK_SHOT );

      //can't see target from here
      if( tr.entityNum == ENTITYNUM_WORLD )
        continue;

      for( j = 0; j < MAX_ZAPS; j++ )
      {
        zap_t *zap = &zaps[ j ];

        for( k = 0; k < zap->numTargets; k++ )
        {
          if( zap->targets[ k ] == enemy )
          {
            foundOldTarget = qtrue;
            break;
          }
        }

        if( foundOldTarget )
          break;
      }

      // enemy is already targetted
      if( foundOldTarget )
        continue;

      return enemy;
    }
  }

  return NULL;
}

/*
===============
G_UpdateZapEffect
===============
*/
static void G_UpdateZapEffect( zap_t *zap )
{
  int       j;
  gentity_t *effect = zap->effectChannel;

  effect->s.eType = ET_LEV2_ZAP_CHAIN;
  effect->classname = "lev2zapchain";
  G_SetOrigin( effect, zap->creator->s.origin );
  effect->s.powerups = zap->creator->s.number;

  effect->s.time = effect->s.time2 = effect->s.constantLight = -1;

  for( j = 0; j < zap->numTargets; j++ )
  {
    int number = zap->targets[ j ]->s.number;

    switch( j )
    {
      case 0: effect->s.time = number;          break;
      case 1: effect->s.time2 = number;         break;
      case 2: effect->s.constantLight = number; break;
      default:                                  break;
    }
  }

  trap_LinkEntity( effect );
}

/*
===============
G_CreateNewZap
===============
*/
static void G_CreateNewZap( gentity_t *creator, gentity_t *target )
{
  int       i, j;
  zap_t     *zap;

  for( i = 0; i < MAX_ZAPS; i++ )
  {
    zap = &zaps[ i ];

    if( !zap->used )
    {
      zap->used = qtrue;

      zap->timeToLive = LEVEL2_AREAZAP_TIME;
      if( g_modAlienRate.integer > 0 )
        zap->timeToLive = zap->timeToLive * g_modAlienRate.integer / 100;
      zap->damageUsed = 0;

      zap->creator = creator;

      zap->targets[ 0 ] = target;
      zap->numTargets = 1;

      for( j = 1; j < MAX_ZAP_TARGETS && zap->targets[ j - 1 ]; j++ )
      {
        zap->targets[ j ] = G_FindNewZapTarget( zap->targets[ j - 1 ] );

        if( zap->targets[ j ] )
          zap->numTargets++;
      }

      zap->effectChannel = G_Spawn( );
      G_UpdateZapEffect( zap );

      return;
    }
  }
}


/*
===============
G_UpdateZaps
===============
*/
void G_UpdateZaps( int msec )
{
  int   i, j;
  zap_t *zap;
  int   damage;

  for( i = 0; i < MAX_ZAPS; i++ )
  {
    zap = &zaps[ i ];

    if( zap->used )
    {
      //check each target is valid
      for( j = 0; j < zap->numTargets; j++ )
      {
        gentity_t *source;
        gentity_t *target = zap->targets[ j ];

        if( j == 0 )
          source = zap->creator;
        else
          source = zap->targets[ j - 1 ];

        if( target->health <= 0 || !target->inuse || //early out
            Distance( source->s.origin, target->s.origin ) > LEVEL2_AREAZAP_RANGE )
        {
          target = zap->targets[ j ] = G_FindNewZapTarget( source );

          //couldn't find a target, so forget about the rest of the chain
          if( !target )
            zap->numTargets = j;
        }
      }

      if( zap->numTargets )
      {
        for( j = 0; j < zap->numTargets; j++ )
        {
          gentity_t *source;
          gentity_t *target = zap->targets[ j ];
          float     r = 1.0f / zap->numTargets;
          float     damageFraction = 2 * r - 2 * j * r * r - r * r;
          vec3_t    forward;

          if( j == 0 )
            source = zap->creator;
          else
            source = zap->targets[ j - 1 ];

          damage = ceil( ( (float)msec / LEVEL2_AREAZAP_TIME ) *
              LEVEL2_AREAZAP_DMG * damageFraction );

          // don't let a high msec value inflate the total damage
          if( damage + zap->damageUsed > LEVEL2_AREAZAP_DMG )
            damage = LEVEL2_AREAZAP_DMG - zap->damageUsed;

          VectorSubtract( target->s.origin, source->s.origin, forward );
          VectorNormalize( forward );

          //do the damage
          if( damage )
          {
            G_Damage( target, source, zap->creator, forward, target->s.origin,
                    damage, DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, MOD_LEVEL2_ZAP, 1.0 );
            if( g_modAlienRate.integer > 0 )
              damage = damage * 100 / g_modAlienRate.integer;
            zap->damageUsed += damage;
          }
        }
      }

      G_UpdateZapEffect( zap );

      zap->timeToLive -= msec;

      if( zap->timeToLive <= 0 || zap->numTargets == 0 || zap->creator->health <= 0 )
      {
        zap->used = qfalse;
        G_FreeEntity( zap->effectChannel );
      }
    }
  }
}

/*
===============
areaZapFire
===============
*/
void areaZapFire( gentity_t *ent )
{
  trace_t   tr;
  vec3_t    end;
  gentity_t *traceEnt;
  vec3_t    mins, maxs;

  VectorSet( mins, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH );
  VectorSet( maxs, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH );

  // set aiming directions
  AngleVectors( ent->client->ps.viewangles, forward, right, up );

  CalcMuzzlePoint( ent, forward, right, up, muzzle );

  VectorMA( muzzle, LEVEL2_AREAZAP_RANGE, forward, end );

  G_UnlaggedOn( ent, muzzle, LEVEL2_AREAZAP_RANGE );
  trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
  G_UnlaggedOff( );

  if( tr.surfaceFlags & SURF_NOIMPACT )
    return;

  traceEnt = &g_entities[ tr.entityNum ];

  if( ( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
      ( traceEnt->s.eType == ET_BUILDABLE &&
        BG_FindTeamForBuildable( traceEnt->s.modelindex ) == BIT_HUMANS ) ) && traceEnt->health > 0 )
  {
    G_CreateNewZap( ent, traceEnt );
  }
}


/*
======================================================================

LEVEL3

======================================================================
*/

/*
===============
CheckPounceAttack
===============
*/
qboolean CheckPounceAttack( gentity_t *ent )
{
  trace_t   tr;
  gentity_t *tent;
  gentity_t *traceEnt;
  int       damage;
  float		kkback;

  if( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
  {
    ent->client->allowedToPounce = qfalse;
    ent->client->pmext.pouncePayload = 0;
  }

  if( !ent->client->allowedToPounce )
    return qfalse;

  if( ent->client->ps.weaponTime )
    return qfalse;

  G_WideTrace( &tr, ent, LEVEL3_POUNCE_RANGE, LEVEL3_POUNCE_WIDTH, &traceEnt );

  if( traceEnt == NULL )
    return qfalse;

  // send blood impact
  if( traceEnt->takedamage && traceEnt->client )
  {
    tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
    tent->s.otherEntityNum = traceEnt->s.number;
    tent->s.eventParm = DirToByte( tr.plane.normal );
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  if( !traceEnt->takedamage )
    return qfalse;

  damage = (int)( ( (float)ent->client->pmext.pouncePayload
    / (float)LEVEL3_POUNCE_SPEED ) * LEVEL3_POUNCE_DMG );

  ent->client->pmext.pouncePayload = 0;

  if( g_improvedGameplay.integer )
    kkback = 1.5;
  else
    kkback = 1;

  G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage,
      DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE, kkback );

  ent->client->allowedToPounce = qfalse;

  return qtrue;
}

void bounceBallFire( gentity_t *ent )
{
  gentity_t *m;

  m = fire_bounceBall( ent, muzzle, forward );

//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
}


/*
======================================================================

LEVEL4

======================================================================
*/

/*
===============
ChargeAttack
===============
*/
void ChargeAttack( gentity_t *ent, gentity_t *victim )
{
  gentity_t *tent;
  int       damage;
  vec3_t    forward, normal;

  if( level.time < victim->chargeRepeat )
    return;

  victim->chargeRepeat = level.time + LEVEL4_CHARGE_REPEAT;

  VectorSubtract( victim->s.origin, ent->s.origin, forward );
  VectorNormalize( forward );
  VectorNegate( forward, normal );

  if( victim->client )
  {
    tent = G_TempEntity( victim->s.origin, EV_MISSILE_HIT );
    tent->s.otherEntityNum = victim->s.number;
    tent->s.eventParm = DirToByte( normal );
    tent->s.weapon = ent->s.weapon;
    tent->s.generic1 = ent->s.generic1; //weaponMode
  }

  if( !victim->takedamage )
    return;

  damage = (int)( ( (float)ent->client->ps.stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) * LEVEL4_CHARGE_DMG );

  G_Damage( victim, ent, ent, forward, victim->s.origin, damage, 0, MOD_LEVEL4_CHARGE, 1.5 );
}

/*
===============
G_CrushAttack

Should only be called if there was an impact between a tyrant and another player
===============
*/
void G_CrushAttack( gentity_t *ent, gentity_t *victim )
{
  vec3_t dir;
  float jump;
  int damage;

  if( !victim->takedamage ||
      ent->client->ps.origin[ 2 ] + ent->r.mins[ 2 ] <
      victim->s.origin[ 2 ] + victim->r.maxs[ 2 ] ||
      ( victim->client &&
        victim->client->ps.groundEntityNum == ENTITYNUM_NONE ) )
    return;

  // Deal velocity based damage to target
  jump = BG_FindJumpMagnitudeForClass( ent->client->ps.stats[ STAT_PCLASS ] );
  damage = ( ent->client->pmext.fallVelocity + jump ) *
           -LEVEL4_CRUSH_DAMAGE_PER_V;
  if( damage < 0 )
    damage = 0;

  // Players also get damaged periodically
  if( victim->client &&
      ent->client->lastCrushTime + LEVEL4_CRUSH_REPEAT < level.time )
  {
    ent->client->lastCrushTime = level.time;
    damage += LEVEL4_CRUSH_DAMAGE;
  }

  if( damage < 1 )
    return;

  // Crush the victim over a period of time
  VectorSubtract( victim->s.origin, ent->client->ps.origin, dir );
  G_Damage( victim, ent, ent, dir, victim->s.origin, damage,
            DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_CRUSH, 0.0 );
}

//======================================================================

/*
===============
CalcMuzzlePoint

set muzzle location relative to pivoting eye
===============
*/
void CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint )
{
  VectorCopy( ent->s.pos.trBase, muzzlePoint );
  muzzlePoint[ 2 ] += ent->client->ps.viewheight;
  VectorMA( muzzlePoint, 1, forward, muzzlePoint );
  VectorMA( muzzlePoint, 1, right, muzzlePoint );
  // snap to integer coordinates for more efficient network bandwidth usage
  SnapVector( muzzlePoint );
}

/*
===============
FireWeapon3
===============
*/
void FireWeapon3( gentity_t *ent )
{
  if( ent->client )
  {
    // set aiming directions
    AngleVectors( ent->client->ps.viewangles, forward, right, up );
    CalcMuzzlePoint( ent, forward, right, up, muzzle );
  }
  else
  {
    AngleVectors( ent->s.angles2, forward, right, up );
    VectorCopy( ent->s.pos.trBase, muzzle );
  }

  // fire the specific weapon
  switch( ent->s.weapon )
  {
    case WP_ALEVEL3_UPG:
      bounceBallFire( ent );
      break;

    case WP_ABUILD2:
      slowBlobFire( ent );
      break;

	case WP_HBUILD: 
    case WP_HBUILD2: 
	  if( ent->client->pers.designatedBuilder )builderShieldFire( ent );
	  break;

    default:
      break;
  }
}

/*
===============
FireWeapon2
===============
*/
void FireWeapon2( gentity_t *ent )
{
  if( ent->client )
  {
    // set aiming directions
    AngleVectors( ent->client->ps.viewangles, forward, right, up );
    CalcMuzzlePoint( ent, forward, right, up, muzzle );
  }
  else
  {
    AngleVectors( ent->s.angles2, forward, right, up );
    VectorCopy( ent->s.pos.trBase, muzzle );
  }

  // fire the specific weapon
  switch( ent->s.weapon )
  {
    case WP_ALEVEL1_UPG:
      poisonCloud( ent );
      break;
    case WP_ALEVEL2_UPG:
      areaZapFire( ent );
      break;

    case WP_LUCIFER_CANNON:
      LCChargeFire( ent, qtrue );
      break;

    case WP_ABUILD:
    case WP_ABUILD2:
    case WP_HBUILD:
    case WP_HBUILD2:
      cancelBuildFire( ent );
      break;

    default:
      break;
  }
}

/*
===============
FireWeapon
===============
*/
void FireWeapon( gentity_t *ent )
{
  if( ent->client )
  {
    // set aiming directions
    AngleVectors( ent->client->ps.viewangles, forward, right, up );
    CalcMuzzlePoint( ent, forward, right, up, muzzle );
  }
  else
  {
    AngleVectors( ent->turretAim, forward, right, up );
    VectorCopy( ent->s.pos.trBase, muzzle );

    // Hive muzzle point is on the tip
    if( ent->s.eType == ET_BUILDABLE && ent->s.modelindex == BA_A_HIVE )
      VectorMA( muzzle, ent->r.maxs[ 2 ], ent->s.origin2, muzzle );
  }


  // paused
  if ( level.paused ) return;

  // fire the specific weapon
  switch( ent->s.weapon )
  {
    case WP_ALEVEL1:
    case WP_ALEVEL1_UPG:
      meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW );
      break;
    case WP_ALEVEL3:
    case WP_ALEVEL3_UPG:
      meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW );
      break;
    case WP_ALEVEL2:
      meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );
      break;
    case WP_ALEVEL2_UPG:
      meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );
      break;
    case WP_ALEVEL4:
      meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW );
      break;

    case WP_BLASTER:
      blasterFire( ent );
      break;
    case WP_MACHINEGUN:
      bulletFire( ent, RIFLE_SPREAD, RIFLE_DMG, MOD_MACHINEGUN );
      break;
    case WP_SHOTGUN:
      shotgunFire( ent );
      break;
    case WP_CHAINGUN:
      bulletFire( ent, CHAINGUN_SPREAD, CHAINGUN_DMG, MOD_CHAINGUN );
      break;
    case WP_FLAMER:
      flamerFire( ent );
      break;
    case WP_PULSE_RIFLE:
      pulseRifleFire( ent );
      break;
    case WP_MASS_DRIVER:
      massDriverFire( ent );
      break;
    case WP_LUCIFER_CANNON:
      LCChargeFire( ent, qfalse );
      break;
    case WP_LAS_GUN:
      lasGunFire( ent );
      break;
    case WP_PAIN_SAW:
      painSawFire( ent );
      break;
    case WP_GRENADE:
	  if( level.instaGibMode ) Blow_up( ent );
	  else throwGrenade( ent );
      break;

    case WP_LOCKBLOB_LAUNCHER:
      lockBlobLauncherFire( ent );
      break;
    case WP_HIVE:
      hiveFire( ent );
      break;
    case WP_TESLAGEN:
      teslaFire( ent );
      break;
    case WP_MGTURRET:
      if (g_noBaseAttack.integer)
         bulletFire( ent, MGTURRET_SPREAD, MGTURRET_DMG*g_quadfactor.integer, MOD_MGTURRET );
      else if (level.instaGibMode )
         bulletFire( ent, MGTURRET_SPREAD, MGTURRET_DMG*1000, MOD_MGTURRET );
      else
         bulletFire( ent, MGTURRET_SPREAD, MGTURRET_DMG, MOD_MGTURRET );
      break;

    case WP_ABUILD:
    case WP_ABUILD2:
      buildFire( ent, MN_A_BUILD );
      break;
	case WP_HBUILD: 
 	case WP_HBUILD2: 
	  buildFire( ent, MN_H_BUILD );
      break;
    default:
      break;
  }
}

/*
===============
Explode players
for instagib
===============
*/
void Blow_up( gentity_t *ent )
{

  gentity_t *m;


    // set directions
    AngleVectors( ent->client->ps.viewangles, forward, right, up );
    CalcMuzzlePoint( ent, forward, right, up, muzzle );
    
  m = launch_grenade2( ent, muzzle, forward );
}
